/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Jan 20, 2005
*
* @author Fabio Zadrozny
*/
package org.python.pydev.editor.codecompletion.revisited.visitors;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.python.pydev.core.FullRepIterable;
import org.python.pydev.core.ILocalScope;
import org.python.pydev.core.IToken;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.structure.FastStack;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceToken;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.Assert;
import org.python.pydev.parser.jython.ast.Attribute;
import org.python.pydev.parser.jython.ast.Call;
import org.python.pydev.parser.jython.ast.ClassDef;
import org.python.pydev.parser.jython.ast.FunctionDef;
import org.python.pydev.parser.jython.ast.Name;
import org.python.pydev.parser.jython.ast.Str;
import org.python.pydev.parser.jython.ast.Tuple;
import org.python.pydev.parser.jython.ast.argumentsType;
import org.python.pydev.parser.jython.ast.exprType;
import org.python.pydev.parser.jython.ast.stmtType;
import org.python.pydev.parser.visitors.NodeUtils;
import org.python.pydev.parser.visitors.scope.ASTEntry;
import org.python.pydev.parser.visitors.scope.SequencialASTIteratorVisitor;
/**
* @author Fabio Zadrozny
*/
public class LocalScope implements ILocalScope {
//the first node from the stack is always the module itself (if it's not there, it means it is a compiled module scope)
public FastStack<SimpleNode> scope = new FastStack<SimpleNode>(20);
public int scopeEndLine = -1;
public int ifMainLine = -1;
/**
* Used to create without an initial scope. It may be changed later by using the getScopeStack() and
* adding tokens.
*/
public LocalScope() {
}
public LocalScope(FastStack<SimpleNode> scope) {
this.scope.addAll(scope);
}
public FastStack<SimpleNode> getScopeStack() {
return scope;
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (!(obj instanceof LocalScope)) {
return false;
}
LocalScope s = (LocalScope) obj;
if (this.scope.size() != s.scope.size()) {
return false;
}
return checkIfScopesMatch(s);
}
public int hashCode() {
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
/**
* @see org.python.pydev.core.ILocalScope#isOuterOrSameScope(org.python.pydev.editor.codecompletion.revisited.visitors.LocalScope)
*/
public boolean isOuterOrSameScope(ILocalScope s) {
if (this.scope.size() > s.getScopeStack().size()) {
return false;
}
return checkIfScopesMatch(s);
}
/**
* @param s the scope we're checking for
* @return if the scope passed as a parameter starts with the same scope we have here. It should not be
* called if the size of the scope we're checking is bigger than the size of 'this' scope.
*/
@SuppressWarnings("unchecked")
private boolean checkIfScopesMatch(ILocalScope s) {
Iterator<SimpleNode> otIt = s.getScopeStack().iterator();
for (Iterator<SimpleNode> iter = this.scope.iterator(); iter.hasNext();) {
SimpleNode element = iter.next();
SimpleNode otElement = otIt.next();
if (element.beginColumn != otElement.beginColumn)
return false;
if (element.beginLine != otElement.beginLine)
return false;
if (!element.getClass().equals(otElement.getClass()))
return false;
String rep1 = NodeUtils.getFullRepresentationString(element);
String rep2 = NodeUtils.getFullRepresentationString(otElement);
if (rep1 == null || rep2 == null) {
if (rep1 != rep2) {
return false;
}
} else if (!rep1.equals(rep2))
return false;
}
return true;
}
/**
* @see org.python.pydev.core.ILocalScope#getAllLocalTokens()
*/
public IToken[] getAllLocalTokens() {
return getLocalTokens(Integer.MAX_VALUE, Integer.MAX_VALUE, false);
}
/**
* @see org.python.pydev.core.ILocalScope#getLocalTokens(int, int, boolean)
*/
public IToken[] getLocalTokens(int endLine, int col, boolean onlyArgs) {
Set<SourceToken> comps = new HashSet<SourceToken>();
for (Iterator<SimpleNode> iter = this.scope.iterator(); iter.hasNext();) {
SimpleNode element = iter.next();
stmtType[] body = null;
if (element instanceof FunctionDef) {
FunctionDef f = (FunctionDef) element;
final argumentsType args = f.args;
for (int i = 0; i < args.args.length; i++) {
String s = NodeUtils.getRepresentationString(args.args[i]);
comps.add(new SourceToken(args.args[i], s, "", "", "", IToken.TYPE_PARAM));
}
if (args.vararg != null) {
String s = NodeUtils.getRepresentationString(args.vararg);
comps.add(new SourceToken(args.vararg, s, "", "", "", IToken.TYPE_PARAM));
}
if (args.kwarg != null) {
String s = NodeUtils.getRepresentationString(args.kwarg);
comps.add(new SourceToken(args.kwarg, s, "", "", "", IToken.TYPE_PARAM));
}
if (args.kwonlyargs != null) {
for (int i = 0; i < args.kwonlyargs.length; i++) {
String s = NodeUtils.getRepresentationString(args.kwonlyargs[i]);
comps.add(new SourceToken(args.kwonlyargs[i], s, "", "", "", IToken.TYPE_PARAM));
}
}
if (onlyArgs) {
continue;
}
body = f.body;
}
else if (element instanceof ClassDef && !iter.hasNext()) {
ClassDef classDef = (ClassDef) element;
body = classDef.body;
}
if (body != null) {
try {
for (int i = 0; i < body.length; i++) {
GlobalModelVisitor visitor = new GlobalModelVisitor(GlobalModelVisitor.GLOBAL_TOKENS, "",
false, true);
stmtType stmt = body[i];
if (stmt == null) {
continue;
}
stmt.accept(visitor);
List<IToken> t = visitor.tokens;
for (Iterator<IToken> iterator = t.iterator(); iterator.hasNext();) {
SourceToken tok = (SourceToken) iterator.next();
//if it is found here, it is a local type
tok.type = IToken.TYPE_LOCAL;
if (tok.getAst().beginLine <= endLine) {
comps.add(tok);
}
}
}
} catch (Exception e) {
Log.log(e);
}
}
}
return (SourceToken[]) comps.toArray(new SourceToken[0]);
}
/**
*
* @param argName this is the argument (cannot have dots)
* @param activationToken this is the actual activation token we're looking for
* (may have dots).
*
* Note that argName == activationToken first part before the dot (they may be equal)
* @return a list of tokens for the local
*/
public Collection<IToken> getInterfaceForLocal(String activationToken) {
return getInterfaceForLocal(activationToken, true, true);
}
public Collection<IToken> getInterfaceForLocal(String activationToken, boolean addAttributeAccess,
boolean addLocalsFromHasAttr) {
Set<SourceToken> comps = new HashSet<SourceToken>();
Iterator<SimpleNode> it = this.scope.topDownIterator();
if (!it.hasNext()) {
return new ArrayList<IToken>();
}
SimpleNode element = it.next();
String dottedActTok = activationToken + '.';
//ok, that's the scope we have to analyze
SequencialASTIteratorVisitor visitor = SequencialASTIteratorVisitor.create(element);
ArrayList<Class> classes = new ArrayList<Class>(2);
if (addAttributeAccess) {
classes.add(Attribute.class);
}
if (addLocalsFromHasAttr) {
classes.add(Call.class);
}
Iterator<ASTEntry> iterator = visitor.getIterator(classes.toArray(new Class[classes.size()]));
while (iterator.hasNext()) {
ASTEntry entry = iterator.next();
if (entry.node instanceof Attribute) {
String rep = NodeUtils.getFullRepresentationString(entry.node);
if (rep.startsWith(dottedActTok)) {
rep = rep.substring(dottedActTok.length());
if (NodeUtils.isValidNameRepresentation(rep)) { //that'd be something that can happen when trying to recreate the parsing
comps.add(new SourceToken(entry.node, FullRepIterable.getFirstPart(rep), "", "", "",
IToken.TYPE_OBJECT_FOUND_INTERFACE));
}
}
} else if (entry.node instanceof Call) {
Call call = (Call) entry.node;
if ("hasattr".equals(NodeUtils.getFullRepresentationString(call.func)) && call.args != null
&& call.args.length == 2) {
String rep = NodeUtils.getFullRepresentationString(call.args[0]);
if (rep.equals(activationToken)) {
exprType node = call.args[1];
if (node instanceof Str) {
Str str = (Str) node;
String attrName = str.s;
if (NodeUtils.isValidNameRepresentation(attrName)) {
comps.add(new SourceToken(node, attrName, "", "", "",
IToken.TYPE_OBJECT_FOUND_INTERFACE));
}
}
}
}
}
}
return new ArrayList<IToken>(comps);
}
/**
* @see org.python.pydev.core.ILocalScope#getLocalImportedModules(int, int, java.lang.String)
*/
public List<IToken> getLocalImportedModules(int line, int col, String moduleName) {
ArrayList<IToken> importedModules = new ArrayList<IToken>();
for (Iterator<SimpleNode> iter = this.scope.iterator(); iter.hasNext();) {
SimpleNode element = iter.next();
if (element instanceof FunctionDef) {
FunctionDef f = (FunctionDef) element;
for (int i = 0; i < f.body.length; i++) {
stmtType stmt = f.body[i];
if (stmt != null) {
importedModules.addAll(GlobalModelVisitor.getTokens(stmt, GlobalModelVisitor.ALIAS_MODULES,
moduleName, null, false));
}
}
}
}
return importedModules;
}
/**
* @see org.python.pydev.core.ILocalScope#getClassDef()
*/
public ClassDef getClassDef() {
for (Iterator<SimpleNode> it = this.scope.topDownIterator(); it.hasNext();) {
SimpleNode node = it.next();
if (node instanceof ClassDef) {
return (ClassDef) node;
}
}
return null;
}
/**
* @see org.python.pydev.core.ILocalScope#isLastClassDef()
*/
public boolean isLastClassDef() {
if (this.scope.size() > 0 && this.scope.peek() instanceof ClassDef) {
return true;
}
return false;
}
public Iterator iterator() {
return scope.topDownIterator();
}
public int getIfMainLine() {
return ifMainLine;
}
public int getScopeEndLine() {
return scopeEndLine;
}
public void setIfMainLine(int original) {
this.ifMainLine = original;
}
public void setScopeEndLine(int beginLine) {
this.scopeEndLine = beginLine;
}
/**
* Constant containing the calls that are checked for implementations.
*
* Couldn't find anything similar for pyprotocols.
*
* Zope has a different heuristic which is also checked:
* assert Interface.implementedBy(foo)
*
* maps the method name to check -> index of the class in the call (or negative if class is the caller)
*
* TODO: This should be made public to the user...
*/
public static final Map<String, Integer> ISINSTANCE_POSSIBILITIES = new HashMap<String, Integer>();
static {
ISINSTANCE_POSSIBILITIES.put("isinstance".toLowerCase(), 2);
ISINSTANCE_POSSIBILITIES.put("IsImplementation".toLowerCase(), 2);
ISINSTANCE_POSSIBILITIES.put("IsInterfaceDeclared".toLowerCase(), 2);
ISINSTANCE_POSSIBILITIES.put("implementedBy".toLowerCase(), -1);
}
/**
* @see {@link ILocalScope#getPossibleClassesForActivationToken(String)}
*/
public List<String> getPossibleClassesForActivationToken(String actTok) {
ArrayList<String> ret = new ArrayList<String>();
Iterator<SimpleNode> it = this.scope.topDownIterator();
if (!it.hasNext()) {
return ret;
}
SimpleNode element = it.next();
//ok, that's the scope we have to analyze
SequencialASTIteratorVisitor visitor = SequencialASTIteratorVisitor.create(element);
Iterator<ASTEntry> iterator = visitor.getIterator(Assert.class);
while (iterator.hasNext()) {
ASTEntry entry = iterator.next();
Assert ass = (Assert) entry.node;
if (ass.test instanceof Call) {
Call call = (Call) ass.test;
String rep = NodeUtils.getFullRepresentationString(call.func);
if (rep == null) {
continue;
}
Integer classIndex = ISINSTANCE_POSSIBILITIES.get(FullRepIterable.getLastPart(rep).toLowerCase());
if (classIndex != null) {
if (call.args != null && (call.args.length >= Math.max(classIndex, 1))) {
//in all cases, the instance is the 1st parameter.
String foundActTok = NodeUtils.getFullRepresentationString(call.args[0]);
if (foundActTok != null && foundActTok.equals(actTok)) {
if (classIndex > 0) {
exprType type = call.args[classIndex - 1];
if (type instanceof Tuple) {
//case: isinstance(obj, (Class1,Class2))
Tuple tuple = (Tuple) type;
for (exprType expr : tuple.elts) {
addRepresentationIfPossible(ret, expr);
}
} else {
//case: isinstance(obj, Class)
addRepresentationIfPossible(ret, type);
}
} else {
//zope case Interface.implementedBy(obj) -> Interface added
ret.add(FullRepIterable.getWithoutLastPart(rep));
}
}
}
}
}
}
return ret;
}
/**
* @param ret the list where the representation should be added
* @param expr the Name or Attribute that determines the class that should be added
*/
private void addRepresentationIfPossible(ArrayList<String> ret, exprType expr) {
if (expr instanceof Name || expr instanceof Attribute) {
String string = NodeUtils.getFullRepresentationString(expr);
if (string != null) {
ret.add(string);
}
}
}
}